-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local c_factory = require 'mc.orm.factory'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local property_changed = org_freedesktop_dbus.ObjMgrPropertiesChanged
local match_rule = org_freedesktop_dbus.MatchRule
local c_network_adapter = require 'device.class.network_adapter'
local card_init = require 'device.class.nic_mgmt.card.card_init'
local comm_defs = require 'device.class.nic_mgmt.comm_defs'
local comm_fun = require 'device.class.nic_mgmt.comm_fun'

local card_mgmt = class()

-- 设备树对象属性到资源树对象属性的映射
local prop_table = {
    VendorId = 'VendorID',
    DeviceId = 'DeviceID',
    SubSystemVendorId = 'SubsystemVendorID',
    SubSystemDeviceId = 'SubsystemDeviceID',
    SocketId = 'SocketId',
    DevBus = 'DevBus',
    Bus = 'Bus',
    Device = 'Device',
    Function = 'Function',
    DevDevice = 'DevDevice',
    DevFunction = 'DevFunction',
    Slot = 'SlotNumber',
    Manufacturer = 'Manufacturer',
    PartNumber = 'PartNumber',
    PcbVersion = 'PCBVersion',
    SerialNumber = 'SerialNumber',
    PredictiveFault = 'PredictiveFault',
    DiagnosticFault = 'DiagnosticFault',
    SpecialPcieCard = 'SpecialPcieCard',
    HotSwappable = 'HotPluggable',
    ReadyToRemove = 'ReadyToRemove',
    LinkSpeed = 'LinkSpeed',
    LinkSpeedCapability = 'LinkSpeedCapability',
    LinkWidth = 'LinkWidth',
    LinkWidthAbility = 'LinkWidthCapability',
    Type = 'Type',
    DeviceName = 'DeviceLocator',
    NetworkPortCount = 'NetworkPortCount',
    ChipModel = 'Model',
    ChipVendor = 'ChipManufacturer',
    SupportedMctp = 'SupportedMctp',
    SupportedLldp = 'SupportedLLDP',
    PfMacInfo = 'PfMacInfo',
    MPUBusyStatus = 'MPUBusyStatus',
    TemperatureCelsius = 'TemperatureCelsius',
    TemperatureStatus = 'TemperatureStatus',
    MaxSFPTemperatureCelsius = 'SFPMaxTemperatureCelsius',
    Health = 'Health',
    FaultState = 'FaultState',
    FaultCode = 'FaultCode',
    Id = 'BoardID',
    RefChip = 'RefChip'
}

local device_interface_list = {
    comm_defs.PCIE_DEVICE_INTERFACE,
    comm_defs.PCIE_CARD_DEVICE_INTERFACE,
    comm_defs.PCIE_DEVICE_HOT_SWAP_INTERFACE,
    comm_defs.PCIE_DEVICE_BANDWIDTH_INTERFACE,
    comm_defs.BOARD_DEVICE_INTERFACE,
    comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE,
    comm_defs.NETWORK_ADAPTER_DEVICE_COOLING_INTERFACE,
    comm_defs.NETWORK_ADAPTER_DEVICE_FAULT_STATUS_INTERFACE,
    comm_defs.NETWORK_ADAPTER_DEVICE_LOG_COLLECTION_INTERFACE
}

-- 处理特殊的属性映射逻辑
local function handle_special_property_mapping(interface, prop, orm_obj, value)
    if interface == comm_defs.PCIE_DEVICE_INTERFACE and prop == 'Location' then
        orm_obj['Position'] = value
    end
    if interface == comm_defs.PCIE_CARD_DEVICE_INTERFACE and prop == 'Name' then
        orm_obj['Name'] = value
    end
    if interface == comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE and prop == 'Description' then
        orm_obj['Description'] = value
        orm_obj['ModelDescription'] = value
    end
    if interface == comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE and prop == 'FirmwareVersion' then
        orm_obj['FirmwareVersion'] = value
        -- 网卡版本更新后，同步到该网卡的所有网口
        local ports = orm_obj.children
        for _, port in pairs(ports) do
            port.FirmwareVersion = value
        end
    end
end

local function listen_device_obj_property_change(bus, sig_slot, card_device_path, orm_obj)
    log:notice('listen_device_obj_property_change start, card_device_path is %s', card_device_path)
    local property_changed_sig = match_rule.signal(property_changed.name, property_changed.interface):with_path(
        card_device_path)
    sig_slot[#sig_slot + 1] = bus:match(property_changed_sig, function(msg)
        local interface, props = msg:read('sa{sv}as')
        for k, v in pairs(props) do
            -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
            pcall(function()
                if prop_table[k] then
                    orm_obj[prop_table[k]] = v:value()
                else
                    handle_special_property_mapping(interface, k, orm_obj, v:value())
                end
            end)
        end
    end)
end

local function synchronize_property(bus, card_device_path, orm_obj)
    log:notice('synchronize_property start, card_device_path is %s', card_device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        card_device_path,
        device_interface_list)
    local card_device_obj, props
    for service_name, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface)
            card_device_obj = mdb.get_object(bus, card_device_path, interface)
            props = bus:call(service_name, card_device_path,
                comm_defs.ORG_PROPERTIES_INTERFACE, 'GetAll', 's', interface)
            for prop in pairs(props) do
                pcall(function()
                    local ret, val = card_device_obj:get_property(prop)
                    if ret ~= 0 then
                        log:error('get_property failed, prop=%s, device_path=%s', prop, card_device_path)
                        goto continue
                    end
                    if prop_table[prop] then
                        orm_obj[prop_table[prop]] = val
                    else
                        handle_special_property_mapping(interface, prop, orm_obj, val)
                    end
                    ::continue::
                end)
            end
        end
    end
end

local function get_node_id_by_device_path(bus, device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        device_path, { comm_defs.PCIE_DEVICE_INTERFACE })
    local card_device_obj
    for _, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface)
            card_device_obj = mdb.get_object(bus, device_path, interface)
            goto continue
        end
    end
    ::continue::
    if not card_device_obj then
        log:notice('get_node_id_by_device_path failed, device_path is %s', device_path)
        return
    end
    local ret, device_name = card_device_obj:get_property('DeviceName')
    if ret ~= 0 then
        log:error('get_node_id_by_device_path failed, device_path is %s', device_path)
        return
    end
    local node_id = string.gsub(device_name, '%s+', '')
    return node_id
end

local function create_card_orm_object(bus, device_path)
    log:notice('create_card_orm_object start, device_path is %s', device_path)
    local node_id = get_node_id_by_device_path(bus, device_path)
    if not node_id then
        log:error('create_card_orm_object failed, because node_id is nil, device_path is %s', device_path)
        return
    end
    local object_name = comm_fun.get_object_name_by_device_path(bus, device_path, 
        {from = "PCIeNicCard", to = "NetworkAdapter"})
    if not object_name then
        log:error('create_card_orm_object failed, because object_name is nil, device_path is %s', device_path)
        return
    end
    local mdb_obj = c_network_adapter.create_mdb_object({
        ID = node_id,
        NodeId = node_id,
        ObjectName = object_name,
        CreatedByDeviceObject = true -- 标识orm对象来源于设备树对象
    })
    c_factory.get_instance():create_object('NetworkAdapter', mdb_obj)
    local card_orm_obj = c_network_adapter.collection:find({
        ID = node_id,
        NodeId = node_id
    })
    if card_orm_obj then
        log:notice('create_card_orm_object success, device_path is %s', device_path)
    else
        log:error('create_card_orm_object failed, device_path is %s', device_path)
    end
    return card_orm_obj
end

function card_mgmt:on_add_card_device_obj()
    log:notice('on_add_card_device_obj start')
    local self_instance = self
    comm_fun.set_interface_add_signal(self.bus, self.sig_slot, comm_defs.CARD_DEVICE_PATH_PATTERN,
        comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE, function(path)
            self_instance:init_obj(path)
        end)
end

function card_mgmt:init_obj(device_path)
    -- 用pcall包裹，避免某一个网卡初始化失败导致循环结束
    pcall(function()
        if self.objects[device_path] then return end
        local card_orm_obj = create_card_orm_object(self.bus, device_path)
        if card_orm_obj then
            self.objects[device_path] = card_orm_obj
            -- 先启监听设备树对象属性变化，更新对应的资源树对象属性
            listen_device_obj_property_change(self.bus, self.sig_slot, device_path, card_orm_obj)
            -- 再将设备树对象属性赋值给对应资源树对象
            synchronize_property(self.bus, device_path, card_orm_obj)
        end
    end)
end

-- 网口资源树对象创建后，注册到网卡的子对象集合中
function card_mgmt:register_children(port_orm_obj, card_orm_obj)
    table.insert(card_orm_obj.children, port_orm_obj)
    -- 当网卡所有的网口子对象创建好后再初始化
    if #card_orm_obj.children == card_orm_obj.NetworkPortCount then
        log:notice('card all ports ready, node_id is %s', card_orm_obj.NodeId)
        card_init.init(card_orm_obj)
    end
end

function card_mgmt:get_orm_obj_by_device_path(card_device_path)
    local card_orm_obj = self.objects[card_device_path]
    if not card_orm_obj then
        log:error(
            'there is not orm obj for card device obj, card_device_path is %s',
            card_device_path)
        return
    end
    return card_orm_obj
end

function card_mgmt:ctor(bus)
    self.bus = bus
    self.objects = {}  -- 网卡设备树对象和资源树对象的映射关系
    self.sig_slot = {} -- 存放设备树对象相关信号
end

function card_mgmt:init()
    log:notice('card_mgmt init')
    -- 先监听网卡设备树对象新增信号
    self:on_add_card_device_obj()
    -- 获取所有的网卡设备树对象，创建对应的orm对象
    local card_device_paths = comm_fun.get_all_device_paths(self.bus, comm_defs.CARD_DEVICE_PATH_PATTERN, 1,
        comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE)
    for _, card_device_path in pairs(card_device_paths) do
        log:notice('get card_device_path: %s', card_device_path)
        self:init_obj(card_device_path)
    end
end

return singleton(card_mgmt)
